package today.belief.mole.util;

/**
 * @Author LXH
 * @Date 2019/12/23
 * @Description IP地址的相关操作
 */
public class Ip {

    /**
     * 判断文本是否为IPV4格式
     * <p>
     * IPV4通常的书写格式是四个0~255的十进制数字用点号连接起来，如：192.168.200.235
     * <p>
     * 每段的高位可以补 0，但是最多每段不超过 3 个字符。例如：允许 010.10.56.15，不允许 0010.10.56.15
     *
     * @param src IP地址
     * @return true-是IPV4格式，false-不是IPV4格式
     */
    public static boolean v4(String src) {
        return v4Numeric(src) != null;
    }

    /**
     * 判断文本是否为IPV6格式
     * <p>
     * IPV6格式比较复杂，完整格式是8个4位的16进制数用冒号连接起来，如：abcd:ef01:2345:6789:abcd:ef01:2345:6789
     * <p>
     * 每段的高位可以补 0，但是最多每段不超过 4 个字符，如：允许 0001:002:0a:4e2b:5:6:7:8，不允许 00001:002:0a:4e2b:5:6:7:8
     * <p>
     * 连续每段为0的可以省略为 ::，如： 1031:0:0000:2345:6789:abcd:ef01:2345 可以简写为 1031::2345:6789:abcd:ef01:2345
     * <p>
     * 只可以省略一次，如：1:0:0:2:0:0:3:4 只能简写为 1::2:0:0:3:4 或者 1:0:0:2::3:4
     * <p>
     * 包含IPV4的格式，可以将最后的2个4位16进制数写成IPV4格式，如： abcd::2345:6789 可以写成  abcd::35.69.103.137
     *
     * @param src IP地址
     * @return true-是IPV6格式，false-不是IPV6格式
     */
    public static boolean v6(String src) {
        return v6Numeric(src) != null;
    }

    private final static int IPV4_SIZE = 4;
    private final static int IPV6_SIZE = 16;

    /**
     * 判断文本是否为IPV4格式，如果是，将每个数字转换byte类型，返回byte数组；否则返回 null
     * <p>
     * 直接将0~255的数字转换为byte类型，放入数组里面
     * <p>
     * 将数组里面的值和255做与运算，得到原数值
     */
    private static byte[] v4Numeric(String src) {
        int length = src.length();
        if (length < 7 || length > 15) {
            return null;
        }
        byte[] result = new byte[IPV4_SIZE];
        // 每段的数值
        int value = 0;
        // 每段的字符个数
        int count = 0;
        // . 的序号
        int dot = 0;
        for (int i = 0; i < length; i++) {
            char c = src.charAt(i);
            if (c == '.') {
                result[dot] = (byte) value;
                dot++;
                if (value > 0xff || dot > 3 || count == 0 || count > 3) {
                    return null;
                }
                value = 0;
                count = 0;
            } else {
                int digit = Character.digit(c, 10);
                if (digit < 0) {
                    return null;
                }
                value *= 10;
                value += digit;
                count++;
            }
        }
        if (value > 0xff || dot != 3 || count == 0 || count > 3) {
            return null;
        }
        result[dot] = (byte) value;
        return result;
    }

    /**
     * 判断文本是否为IPV6格式，如果是，将每个数字转换两个byte类型，返回byte数组；否则返回 null
     * <p>
     * 将每段数字的每两位转换为一个byte类型，放入数组里面，如：4e2a::，将4e转换为一个byte，将2a转换为一个byte
     * <p>
     * 将数组里面的值和255做与运算，得到原数值
     */
    private static byte[] v6Numeric(String src) {
        if (src.length() < 2) {
            return null;
        }

        byte[] result = new byte[IPV6_SIZE];
        // 每段的数值
        int val = 0;
        // byte数组的下标
        int index = 0;

        char[] chars = src.toCharArray();
        int length = chars.length;

        // 当前字符
        char ch;
        // 当前字符的下标
        int i = 0;
        // 每段数字的字符个数
        int count = 0;

        if (chars[i] == ':') {
            if (chars[++i] != ':') {
                return null;
            }
        }
        // 上一个字符是否是十六进制的数字
        boolean isDigit = false;
        // 出现连续冒号时，省略的数段本应在数组中的位置的下标
        int colon = -1;
        // 包含IPV4格式时，IPV4中第一个字符的下标
        int ipv4Index = i;
        while (i < length) {
            ch = chars[i++];
            int digit = Character.digit(ch, 16);
            if (digit != -1) {
                val <<= 4;
                val |= digit;
                if (val > 0xffff) {
                    return null;
                }
                isDigit = true;
                count++;
                if (count > 4) {
                    return null;
                }
                continue;
            }
            if (ch == ':') {
                count = 0;
                ipv4Index = i;
                if (!isDigit) {
                    if (colon != -1) {
                        return null;
                    }
                    // 出现连续的冒号
                    colon = index;
                    continue;
                } else if (i == length) {
                    // 上一个字符是数字，当前字符是冒号，不能作为结尾
                    return null;
                }
                if (index + 2 > IPV6_SIZE) {
                    // 上一个字符是数字，当前字符是冒号，那么后面还有数字，前面的数字段不能大于8个
                    return null;
                }
                result[index++] = (byte) ((val >> 8) & 0xff);
                result[index++] = (byte) (val & 0xff);
                isDigit = false;
                val = 0;
                continue;
            }
            // 包含IPV4格式的情况
            if (ch == '.' && ((index + IPV4_SIZE) <= IPV6_SIZE)) {
                String ipv4 = src.substring(ipv4Index, length);
                // . 的个数
                int dot = 0;
                int j = 0;
                while ((j = ipv4.indexOf('.', j)) != -1) {
                    dot++;
                    j++;
                }
                if (dot != 3) {
                    return null;
                }

                byte[] v4addr = v4Numeric(ipv4);
                if (v4addr == null) {
                    return null;
                }
                for (int k = 0; k < IPV4_SIZE; k++) {
                    result[index++] = v4addr[k];
                }
                isDigit = false;
                break;
            }
            return null;
        }
        if (isDigit) {
            // 最后一个字符是数字，并且不包含IPV4格式的情况
            if (index + 2 > IPV6_SIZE) {
                return null;
            }
            result[index++] = (byte) ((val >> 8) & 0xff);
            result[index++] = (byte) (val & 0xff);
        }

        if (colon != -1) {
            // 出现连续冒号时，需要补位
            int n = index - colon;
            if (index == IPV6_SIZE) {
                return null;
            }
            for (i = 1; i <= n; i++) {
                result[IPV6_SIZE - i] = result[colon + n - i];
                result[colon + n - i] = 0;
            }
            index = IPV6_SIZE;
        }
        if (index != IPV6_SIZE) {
            return null;
        }

        // IPV4 映射的 IPV6 格式
        byte[] newResult = convert(result);
        if (newResult != null) {
            return newResult;
        } else {
            return result;
        }
    }

    /**
     * 如果是IPV4映射的IPV6格式，转换为长度为4的字节数组
     */
    private static byte[] convert(byte[] address) {
        if (isv4(address)) {
            byte[] newAddr = new byte[IPV4_SIZE];
            System.arraycopy(address, 12, newAddr, 0, IPV4_SIZE);
            return newAddr;
        }
        return null;
    }

    /**
     * 判断是否为IPV4映射的IPV6格式
     * <p>
     * IPV4映射成IPV6格式 1.2.3.4  -->  ::ffff:1.2.3.4
     *
     * @param address ip地址
     * @return true-是，false-否
     */
    private static boolean isv4(byte[] address) {
        if (address.length < IPV6_SIZE) {
            return false;
        }
        return (address[0] == 0x00) && (address[1] == 0x00) &&
                (address[2] == 0x00) && (address[3] == 0x00) &&
                (address[4] == 0x00) && (address[5] == 0x00) &&
                (address[6] == 0x00) && (address[7] == 0x00) &&
                (address[8] == 0x00) && (address[9] == 0x00) &&
                (address[10] == (byte) 0xff) &&
                (address[11] == (byte) 0xff);
    }
}

